﻿/*
COPYRIGHT (C) 2009 EPISERVER AB

THIS FILE IS PART OF REMOTE PAGE PROVIDER FOR EPISERVER CMS.
REMOTE PAGE PROVIDER FOR EPISERVER CMS IS FREE SOFTWARE: YOU CAN REDISTRIBUTE IT AND/OR MODIFY IT UNDER THE TERMS OF THE GNU LESSER GENERAL PUBLIC LICENSE VERSION v2.1 AS PUBLISHED BY THE FREE SOFTWARE FOUNDATION.

REMOTE PAGE PROVIDER FOR EPISERVER CMS IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, BUT WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. SEE THE GNU LESSER GENERAL PUBLIC LICENSE FOR MORE DETAILS.
YOU SHOULD HAVE RECEIVED A COPY OF THE GNU LESSER GENERAL PUBLIC LICENSE ALONG WITH REMOTE PAGE PROVIDER FOR EPISERVER CMS]. IF NOT, SEE <HTTP://WWW.GNU.ORG/LICENSES/>.
*/
using System;
using System.Collections.Generic;
using System.Text;
using EPiServer.Web.Hosting;
using EPiServer.Security;
using System.IO;
using System.Collections.Specialized;
using System.Security.Principal;
using System.Web.Hosting;
using EPiServer.Web;
using System.Web;
using EPiServer.Core;
using System.ServiceModel;

namespace EPiServer.Labs.RemoteProvider
{
    public class RemoteVirtualPathProvider  : VirtualPathUnifiedProvider, IFileResolver
    {
        IRemoteProvider                                 _remoteFileProvider;
        RemoteVirtualPathProviderAggregate              _remoteVirtualPathProviderAggregate;
        InitializeData                                  _initializeData;
        object                                          _syncObject = new object();
        bool                                            _inited = false;


        #region Constructor
        public RemoteVirtualPathProvider(string name, NameValueCollection configParameters)
            : base(name, configParameters)
        {
            base.ValidateAndSetupConfigParams();
            
            VirtualPathRoot = VirtualPathUtilityEx.ToAbsolute(ConfigurationParameters["virtualPath"]);
            if (String.IsNullOrEmpty(VirtualPathRoot))
            {
                throw new EPiServerException("VirtualPathNativeProvider configuration missing or empty attribute 'virtualPath'");
            }
     
            string proxyUserName = ConfigurationParameters.Get("proxyUserName");
            string proxyPassword = ConfigurationParameters.Get("proxyUserPassword");
            string _endPoint = ConfigurationParameters["endpointName"];
            _initializeData = new InitializeData();
            
            _initializeData.Credential = new ProviderCredentials();
            if (!string.IsNullOrEmpty(ConfigurationParameters.Get("userName")))
            {
                _initializeData.Credential = new ProviderCredentials(ConfigurationParameters.Get("userName"), ConfigurationParameters.Get("password"));
            }      
      
            _remoteFileProvider = new RemoteProviderClient(_endPoint, proxyUserName, proxyPassword, _initializeData);

            ((RemoteProviderClient)_remoteFileProvider).CommunicationOpened += new EventHandler(RemoteProviderClient_CommunicationOpened);
            ((RemoteProviderClient)_remoteFileProvider).CommunicationClosed += new EventHandler(RemoteProviderClient_CommunicationClosed);
        }

        #endregion

        #region Communication Event
        void RemoteProviderClient_CommunicationClosed(object sender, EventArgs e)
        {
            _inited = false;
        }

        void RemoteProviderClient_CommunicationOpened(object sender, EventArgs e)
        {
            if (_initializeData.RemoteVirtualPathProvidersInfo == null)
            {
              _remoteFileProvider.Initialize(ref _initializeData);
            }
        }
        #endregion

        #region VirtualPathUnifiedProvider implementation

        /// <summary>
        /// Queries the access.
        /// </summary>
        /// <param name="virtualPath">The virtual path.</param>
        /// <param name="user">The user.</param>
        /// <returns></returns>
        /// <remarks>We should take care security</remarks>
        public override AccessLevel QueryAccess(string virtualPath, IPrincipal user)
        {
            return AccessLevel.FullAccess;
        }

        /// <summary>
        /// Gets a value that indicates whether a file exists in the virtual file system.
        /// </summary>
        /// <param name="virtualPath">The path to the virtual file.</param>
        /// <returns>
        /// true if the file exists in the virtual file system; otherwise, false.
        /// </returns>
        public override bool FileExists(string virtualPath)
        {
            if (!CheckVirtualPath(virtualPath))
            {
                return Previous.FileExists(virtualPath);
            }

            if (!CheckAndFixConnection(true))
            {
                return Previous.FileExists(virtualPath);                
            }

            string remoteVirtualPath;
            RemoteUnifiedDirectory remoteUnifiedDir;
            if (!RemoteVirtualPathProviderAggregate.TryToFindRemoteVirtualPath(virtualPath, out remoteUnifiedDir, out remoteVirtualPath))
            {
                return Previous.FileExists(virtualPath);
            }

            if (!RemoteFileProvider.FileExists(remoteVirtualPath))
            {
                return Previous.FileExists(virtualPath);
            }
            return true;
        }

        /// <summary>
        /// Directories the exists.
        /// </summary>
        /// <param name="virtualPath">The virtual path.</param>
        /// <returns></returns>
        public override bool DirectoryExists(string virtualPath)
        {

            if (!CheckVirtualPath(virtualPath))
            {
                return Previous.DirectoryExists(virtualPath);
            } 

            if (!CheckAndFixConnection(true))
            {
                return Previous.DirectoryExists(virtualPath);
            }

            string remoteVirtualPath;
            RemoteUnifiedDirectory remoteUnifiedDir;
            if (!RemoteVirtualPathProviderAggregate.TryToFindRemoteVirtualPath(virtualPath, out remoteUnifiedDir, out remoteVirtualPath))
            {
                return Previous.DirectoryExists(virtualPath);
            }

            if (!RemoteFileProvider.DirectoryExists(VirtualPathUtility.AppendTrailingSlash(remoteVirtualPath)))
            {
                return Previous.FileExists(virtualPath);
            }
            return true;
        }

        /// <summary>
        /// Creates a cache dependency based on the specified virtual paths.
        /// </summary>
        /// <param name="virtualPath">The path to the primary virtual resource.</param>
        /// <param name="virtualPathDependencies">An array of paths to other resources required by the primary virtual resource.</param>
        /// <param name="utcStart">The UTC time at which the virtual resources were read.</param>
        /// <returns>
        /// A <see cref="T:System.Web.Caching.CacheDependency"/> object for the specified virtual resources.
        /// </returns>
        public override System.Web.Caching.CacheDependency GetCacheDependency(string virtualPath, System.Collections.IEnumerable virtualPathDependencies, DateTime utcStart)
        {
            return null;
        }

        /// <summary>
        /// Gets a virtual file from the virtual file system.
        /// </summary>
        /// <param name="virtualPath">The path to the virtual file.</param>
        /// <returns>
        /// A descendent of the <see cref="T:System.Web.Hosting.VirtualFile"/> class that represents a file in the virtual file system.
        /// </returns>
        public override VirtualFile GetFile(string virtualPath)
        {
            if (!CheckVirtualPath(virtualPath))
            {
                return Previous.GetFile(virtualPath);
            }
            
            if (!CheckAndFixConnection(true))
            {
                return Previous.GetFile(virtualPath);
            }

            string remoteVirtualPath;
            RemoteUnifiedDirectory remoteUnifiedDir;
            

            if (!RemoteVirtualPathProviderAggregate.TryToFindRemoteVirtualPath(virtualPath, out remoteUnifiedDir, out remoteVirtualPath))
            {
                return Previous.GetFile(virtualPath);
            }

            RemoteFileInfo remoteFileInfo;
            if (!RemoteFileProvider.GetFile(remoteVirtualPath, out remoteFileInfo))
            {
                return Previous.GetFile(virtualPath);
            }

            remoteFileInfo.VirtualPath = RemoteVirtualPathToLocalVirtualPath(remoteFileInfo.VirtualPath);
            return new RemoteUnifiedFile(RemoteFileProvider, RemoteVirtualPathProviderAggregate.Root, this, remoteFileInfo);
        }

        /// <summary>
        /// Gets the directory.
        /// </summary>
        /// <param name="virtualPath">The virtual path.</param>
        /// <returns></returns>
        public override VirtualDirectory GetDirectory(string virtualPath)
        {
            if (!CheckVirtualPath(virtualPath))
            {
                return Previous.GetDirectory(virtualPath);
            }

            if (!CheckAndFixConnection(true))
            {
                return Previous.GetDirectory(virtualPath);
            }

            string remoteVirtualPath;
            RemoteUnifiedDirectory remoteUnifiedDir;
            if (!RemoteVirtualPathProviderAggregate.TryToFindRemoteVirtualPath(virtualPath, out remoteUnifiedDir, out remoteVirtualPath))
            {
                return Previous.GetDirectory(virtualPath);
            }

            if (RemoteVirtualPathProviderAggregate.Root == remoteUnifiedDir)
            {
                return RemoteVirtualPathProviderAggregate.Root;
            }

            RemoteDirectoryInfo remoteDirectoryInfo;
            if (!RemoteFileProvider.GetDirectory(VirtualPathUtility.AppendTrailingSlash(remoteVirtualPath), out remoteDirectoryInfo))
            {
                return Previous.GetDirectory(virtualPath);
            }

            remoteDirectoryInfo.VirtualPath = RemoteVirtualPathToLocalVirtualPath(remoteDirectoryInfo.VirtualPath);
            remoteDirectoryInfo.IsFirstLevel = false;
            remoteDirectoryInfo.Parent = remoteUnifiedDir.DirInfo;
            return new RemoteUnifiedDirectory(RemoteFileProvider, this, remoteDirectoryInfo);
        }
        #endregion

        #region IFileResolver Members
        /// <summary>
        /// Resolves the file by GUID.
        /// </summary>
        /// <param name="guid">The GUID.</param>
        /// <returns>The Virtual Path</returns>
        public string ResolveFile(Guid guid)
        {
            if (!CheckAndFixConnection(false))
            {
                return null;
            }

            string remoteVirtualPath = RemoteFileProvider.ResolveFileByGuid(guid);
            if (!string.IsNullOrEmpty(remoteVirtualPath))
            {
                return RemoteVirtualPathToLocalVirtualPath(remoteVirtualPath);
            }
            return null;
        }

        /// <summary>
        /// Resolves the file by Virtual Path.
        /// </summary>
        /// <param name="virtualPath">The virtual path.</param>
        /// <param name="guid">The GUID.</param>
        /// <returns>URI of the virtual path</returns>
        public string ResolveFile(string virtualPath, out Guid guid)
        {
            guid = Guid.Empty;

            if (!CheckVirtualPath(virtualPath))
            {
                return null;
            }

            if (!CheckAndFixConnection(true))
            {
                return null;
            }

            if (RemoteVirtualPathProviderAggregate.SupportFileResolver(virtualPath))
            {
                string remoteVirtualPath = LocalVirtualPathToRemoteVirtualPath(virtualPath);
                RemoteFileProvider.ResolveFileByVirtualPath(remoteVirtualPath, out guid);
                if (guid != null && guid != Guid.Empty)
                {
                    return virtualPath;
                }
            }
            return null;
        }
        #endregion

        #region Helper Method
        /// <summary>
        /// Convert the Local Local virtual path to remote virtual path.
        /// </summary>
        /// <param name="virtualPath">The virtual path.</param>
        /// <returns></returns>
        public string LocalVirtualPathToRemoteVirtualPath(string localVirtualPath)
        {
            string remoteVirtualPath;
            RemoteUnifiedDirectory remoteUnifiedDir;
            if (RemoteVirtualPathProviderAggregate.TryToFindRemoteVirtualPath(localVirtualPath, out remoteUnifiedDir, out remoteVirtualPath))
            {
                return remoteVirtualPath;
            }
            return localVirtualPath;
        }

        /// <summary>
        /// Convert the Remote virtual path to local virtual path.
        /// </summary>
        /// <param name="remoteVirtualPath">The Remote virtual path.</param>
        /// <returns></returns>
        public string RemoteVirtualPathToLocalVirtualPath(string remoteVirtualPath)
        {
            if (!remoteVirtualPath.StartsWith(VirtualPathRoot))
            {
                return VirtualPathRoot + (!string.IsNullOrEmpty(remoteVirtualPath) ? remoteVirtualPath.Substring(1) : "");
            }
            return remoteVirtualPath;
        }

        private bool CheckVirtualPath(string virtualPath)
        {
            return virtualPath.StartsWith(VirtualPathRoot);
        }

        private bool CheckAndFixConnection(bool fixConnection)
        {
            if (RemoteFileProvider.GetCommunicationState() != CommunicationState.Opened || _initializeData.RemoteVirtualPathProvidersInfo == null)
            {
                if (fixConnection)
                {
                    RemoteFileProvider.Initialize(ref _initializeData);
                }
            }
            return (_initializeData.RemoteVirtualPathProvidersInfo != null);
        }

        #endregion
       
        #region Property

        /// <summary>
        /// Gets the remote virtual path providers.
        /// </summary>
        /// <value>The remote virtual path providers.</value>
        public RemoteVirtualPathProvidersInfo RemoteVirtualPathProviders
        {
            get
            {
                if (RemoteFileProvider.GetCommunicationState() != CommunicationState.Opened)
                {
                    throw new Exception("The Communication is NOT estabilished");
                }
                
                if (_initializeData.RemoteVirtualPathProvidersInfo != null)
                {
                    return _initializeData.RemoteVirtualPathProvidersInfo;
                }
                
                if (_initializeData.RemoteVirtualPathProvidersInfo == null)
                {
                    lock (_syncObject)
                    {
                        if (_initializeData.RemoteVirtualPathProvidersInfo == null)
                        {
                            RemoteFileProvider.Initialize(ref _initializeData);
                        }
                    }
                }
                return _initializeData.RemoteVirtualPathProvidersInfo;
            }
        }

        /// <summary>
        /// Gets the remote file provider.
        /// </summary>
        /// <value>The remote file provider.</value>
        public IRemoteProvider RemoteFileProvider
        {
            get
            {
                if (!_inited)
                {
                    lock (_syncObject)
                    {
                        if (!_inited)
                        {
                            try
                            {
                                _remoteFileProvider.Initialize(ref _initializeData);
                                _inited = true;
                            }
                            catch (CommunicationException)
                            {
                            }
                        }
                    }
                }
                return _remoteFileProvider;
            }
        }

        public RemoteVirtualPathProviderAggregate RemoteVirtualPathProviderAggregate
        {
            get
            {
                if (_remoteVirtualPathProviderAggregate == null)
                {
                    lock (_syncObject)
                    {
                        if (_remoteVirtualPathProviderAggregate == null)
                        {
                            _remoteVirtualPathProviderAggregate = new RemoteVirtualPathProviderAggregate(this);
                            _remoteVirtualPathProviderAggregate.Init();
                        }
                    }
                }
                return _remoteVirtualPathProviderAggregate;
            }
        }


        /// <summary>
        /// Gets the top directories which is equal to remote virtual path providers.
        /// </summary>
        /// <value>The directories.</value>
        public RemoteUnifiedDirectory[] Directories
        {
            get
            {
                return RemoteVirtualPathProviderAggregate.RemoteVirtualDirectories;
            }
        }
        #endregion


    }
}
